package kino.command;

import kino.client.KinoClient;
import kino.common.KinoConstant;
import kino.database.KinoDB;
import kino.object.KinoObject;
import kino.object.KinoObjectTypeEnum;
import kino.utils.CommonUtil;

import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;

@KinoCommandEnable
public class HScanCommand extends AbstractKinoCommand{
    @Override
    public String doExecute(KinoClient client, String... argv) {
        ScanResult result = new ScanResult();
        if (this.isNumeric(argv[1])) {
            result.setNextCursor(Integer.valueOf(argv[1]));
        } else {
            return KinoConstant.cursor;
        }
        List<Object> res = new ArrayList<>();
        Map<String, Object> extParam = client.getExtParam();
        Map<String, KinoObject> dict = client.getDb().getDict();
        if (!dict.containsKey(argv[0])) {
            res.add("0");
            res.add(Collections.EMPTY_LIST);
            return commandEncoder.encode(res);
        }
        KinoObject object = dict.get(argv[0]);
        if (object.getType() != KinoObjectTypeEnum.KINO_HASH) {
            return KinoConstant.wrongtypeerr;
        }
        do {
            Integer count = extParam != null ? (Integer)extParam.get("COUNT") : null;
            count = count == null ? 10 : count;
            HashMap<String, String> hash = (HashMap<String, String>)object.getObj();
            result = this.getHashSlot(result == null ? 0 : result.nextCursor, hash, count);
            String match = extParam != null ? (String)extParam.get("MATCH") : null;
            List<String> keys = result.getKeys();
            keys = keys.stream().filter(item -> {
                if (match == null || match.length() == 0 || match == "*" || CommonUtil.isMatchString(item, match)) {
                    return true;
                }
                return false;
            }).collect(Collectors.toList());
            if (keys.size() > 0 || result.getNextCursor() == 0) {
                res.add(result.getNextCursor().toString());
                List<String> keyAndValue = new ArrayList<>();
                for (String key : keys) {
                    keyAndValue.add(key);
                    keyAndValue.add(hash.get(key));
                }
                res.add(keyAndValue);
                break;
            }
        } while (result.nextCursor != 0);
        return commandEncoder.encode(res);
    }

    @Override
    public Integer getArgc() {
        return 2;
    }

    @Override
    public String getCommandName() {
        return "hscan";
    }

    @Override
    public boolean aofFlag() {
        return false;
    }

    @Override
    public boolean hasExtParam() {
        return true;
    }

    private class ScanResult {

        private Integer nextCursor;

        private List<String> keys;

        public Integer getNextCursor() {
            return nextCursor;
        }

        public void setNextCursor(Integer nextCursor) {
            this.nextCursor = nextCursor;
        }

        public List<String> getKeys() {
            return keys;
        }

        public void setKeys(List<String> keys) {
            this.keys = keys;
        }
    }
    /**
     * 根据偏移量去探db的1个hash槽
     */
    private ScanResult getHashSlot(int cursor, HashMap<String, String> hashObj, Integer count) {
        ScanResult scanResult = new ScanResult();
        List<String> keys = new ArrayList<>();
        scanResult.setKeys(keys);
        scanResult.setNextCursor(0);
        try {
            Field tableField = hashObj.getClass().getDeclaredField("table");
            tableField.setAccessible(true);
            Object[] table = (Object[])tableField.get(hashObj);
            int slowLen = table.length;
            int thisCursor = cursor >= slowLen ? 0 : cursor;
            int toCursor = count == null ? slowLen - 1 : cursor + count >= slowLen ? slowLen - 1 : cursor + count - 1;
            for (int i = thisCursor ; i <= toCursor ; i++) {
                Class clsHashMap$Node = Class.forName("java.util.HashMap$Node");
                Field nodeField = clsHashMap$Node.getDeclaredField("next");
                Field keyField = clsHashMap$Node.getDeclaredField("key");
                nodeField.setAccessible(true);
                keyField.setAccessible(true);
                Object o = table[i];
                do {
                    if (o != null) {
                        keys.add((String)keyField.get(o));
                    }
                } while (o != null && (o = nodeField.get(o)) != null);
            }
            scanResult.setNextCursor(toCursor == slowLen - 1 ? 0 : toCursor + 1);
        } catch (NoSuchFieldException e) {
            // 空
        } catch (IllegalAccessException e) {
            // 空
        } catch (ClassNotFoundException e) {
            // 空
        }
        return scanResult;
    }
    protected AbstractKinoCommand.ExtParamParseResult doExtParamParse(String... argv) {
        AbstractKinoCommand.ExtParamParseResult paramParseResult = new AbstractKinoCommand.ExtParamParseResult();
        paramParseResult.setSuccess(true);
        Map<String, Object> extParam = new HashMap<>();
        paramParseResult.setExtParam(extParam);
        for (int i = this.getArgc(); i < argv.length;i++) {
            String param = argv[i];
            String upParam = param.toUpperCase(Locale.ROOT);
            if (upParam.equals("MATCH")) {
                if (i == argv.length - 1) {
                    paramParseResult.setSuccess(false);
                    paramParseResult.setErrorMsg(KinoConstant.syntaxerr);
                    return paramParseResult;
                }
                String nextParam = argv[i + 1];
                i++;
                extParam.put(upParam, nextParam);
            } else if (upParam.equals("COUNT")) {
                // 该参数后面应该再跟一个数字，后面不够提示错误
                if (i == argv.length - 1) {
                    paramParseResult.setSuccess(false);
                    paramParseResult.setErrorMsg(KinoConstant.syntaxerr);
                    return paramParseResult;
                }
                String nextParam = argv[i + 1];
                // 下一个参数不是数字的情况，提示错误
                if (!this.isNumeric(nextParam)) {
                    paramParseResult.setSuccess(false);
                    paramParseResult.setErrorMsg(commandEncoder.error("命令" + upParam + "后面需要跟数字"));
                    return paramParseResult;
                }
                i++;
                extParam.put(upParam, Integer.valueOf(nextParam));
            } else {
                paramParseResult.setSuccess(false);
                paramParseResult.setErrorMsg(KinoConstant.syntaxerr);
                return paramParseResult;
            }
        }
        return paramParseResult;
    }
}
